//
//
#ifndef TrfMat2D_h__
#define TrfMat2D_h__

#include "Gmath.h"

namespace Geometric
{
	class TrfMat2D
	{
	public:
		TrfMat2D()
		{
			initialize();
		}

		TrfMat2D(const TrfMat2D& _mat)
		{
			set(_mat.m_mat, 9);
		}

		~TrfMat2D()
		{
		}

	public:
		// store transformation matrix
		double m_mat[3][3];

	public:
		inline void initialize()
		{
			for(unsigned int i = 0; i < 3; i++)
			{
				for(unsigned int j = 0; j < 3; j++)
				{
					m_mat[i][j] = (i==j) ? 1:0;
				}
			}
		}

		inline void set(double* _array, unsigned int _length = 9)
		{
			for(unsigned int n = 0; n < _length; n++)
			{
				unsigned int i = n / 3;
				unsigned int j = n % 3;
				m_mat[i][j] = _array[n];
			}
		}

		inline void merge(const TrfMat2D& _mat)
		{
			double tmp_mat[3][3];

			// temporary store previous matrix
			for(unsigned int i = 0; i < 3; i++)
			{
				for(unsigned int j = 0; j < 3; j++)
				{
					tmp_mat[i][j] = m_mat[i][j];
				}
			}

			// merge two transformation matrix
			for(unsigned int i = 0; i < 3; i++)
			{
				for(unsigned int j = 0; j < 3; j++)
				{
					// set matrix[i][j]
					m_mat[i][j] = 0;
					for(unsigned int k = 0; k < 3; k++)
					{
						m_mat[i][j] += _mat.m_mat[i][k] * tmp_mat[k][j];
					}
				}
			}
		}


	public:
		// abstract: translate (x,y)
		inline void translate(double _x, double _y)
		{
			for(unsigned int j = 0; j < 3; j++)
			{
				m_mat[0][j] += _x*m_mat[2][j];
				m_mat[1][j] += _y*m_mat[2][j];
			}
		}

		// abstract: rotate according to counterclockwise
		inline void rotate(double _angle)
		{
			double tmp_cos = ::cos(_angle);
			double tmp_sin = ::sin(_angle);

			for(unsigned int j = 0; j < 3; j++)
			{
				double mat_0_j = m_mat[0][j];
				double mat_1_j = m_mat[1][j];

				m_mat[0][j] = tmp_cos*mat_0_j - tmp_sin*mat_1_j;
				m_mat[1][j] = tmp_sin*mat_0_j + tmp_cos*mat_1_j;
			}
		}

		// rotate according to a special point
		inline void rotate(double _x, double _y, double _angle)
		{
			translate(-_x, -_y);
			rotate(_angle);
			translate(_x, _y);
		}


		// abstract: scale (x,y)
		inline void scale(double _scale_x, double _scale_y)
		{
			for(unsigned int i = 0; i < 3; i++)
			{
				m_mat[i][0] *= _scale_x;
				m_mat[i][1] *= _scale_y;
			}
		}

		// scale according to a point
		inline void scale(double _x, double _y, double _scale_x, double _scale_y)
		{
			translate(-_x, -_y);
			scale(_scale_x, _scale_y);
			translate(_x, _y);
		}

		// reflect according to a point
		inline void reflection(double _x, double _y)
		{
			// translate (_x,_y) to origin
			translate(-_x, -_y);
			
			for(unsigned int j = 0; j < 3; j++)
			{
				m_mat[0][j] = -m_mat[0][j];
				m_mat[1][j] = -m_mat[1][j];
			}
			
			// translate back
			translate(_x, _y);
		}

		inline void reflection(double _x, double _y, double _dir_x, double _dir_y)
		{
			double angle = ::atan2(_dir_y, _dir_x);

			// translate the point (_x,_y) to origin
			translate(-_x, -_y);

			// rotate the line to X-Axis
			rotate(-angle);
			// reflection about X-Axis
			for(unsigned int j = 0; j < 3; j++)
			{
				m_mat[1][j] = -m_mat[1][j];
			}
			// rotate X-Axis back to the line
			rotate(angle);

			// translate back
			translate(_x, _y);
		}

		inline void shear(double _shx, double _shy)
		{
			for(unsigned int j = 0; j < 3; j++)
			{
				double mat_0_j = m_mat[0][j];
				double mat_1_j = m_mat[1][j];

				m_mat[0][j] = mat_0_j + _shx*mat_1_j;
				m_mat[1][j] = _shy*mat_0_j + mat_1_j;
			}
		}
	};
}
#endif